package main

import (
	"context"
	"errors"
	"flag"
	"log"
	"net"
	"net/http"
	"os"
	"os/exec"
	"os/signal"
	"syscall"
	"time"
)

var (
	ser        *http.Server
	listener   net.Listener
	reloadFlag = flag.Bool("reload", false, "reload http server")
	prodFlag   = flag.Bool("prod", false, "running in prod")
)

func main() {
	flag.Parse()
	ser = getHttpServer("8081", *prodFlag)

	if *reloadFlag {
		log.Println("Listening to existing file descriptor 3.")
		// 如果命令行中包含`reload`，则监听继承的文件描述符
		f := os.NewFile(3, "")
		ln, err := net.FileListener(f)
		listener = ln
		if err != nil {
			panic(err)
		}
	} else {
		ln, err := net.Listen("tcp", ser.Addr)
		listener = ln
		if err != nil {
			panic(err)
		}
	}

	// 子协程启动http服务
	go func() {
		err := ser.Serve(listener)
		if err != http.ErrServerClosed {
			log.Println(err)
		}
	}()

	// 主协程监听进程信号，(stop/reload)服务
	signalHandler()

}

// 重启服务
func reload() error {
	tl, ok := listener.(*net.TCPListener)
	if !ok {
		return errors.New("listener is not tcp listener")
	}

	f, err := tl.File()
	if err != nil {
		return err
	}

	args := make([]string, 0)
	for k, v := range os.Args {
		if k > 0 {
			args = append(args, v)
		}
	}
	//加上reload启动服务
	args = append(args, "-reload")
	cmd := exec.Command(os.Args[0], args...)
	cmd.Stdin = os.Stdin
	cmd.Stdout = os.Stdout
	cmd.ExtraFiles = []*os.File{f} //把tcp文件描述符传递过去
	return cmd.Start()
}

// 信号监听处理
func signalHandler() {
	ch := make(chan os.Signal, 1)

	// kill (no param) default send syscall.SIGTERM
	// kill -2 is syscall.SIGINT
	// kill -9 is syscall.SIGKILL but can't be catch, so don't need add it
	signal.Notify(ch, syscall.SIGINT, syscall.SIGTERM, syscall.SIGUSR2)

	ctx, _ := context.WithTimeout(context.Background(), time.Second*3)
	for {
		sig := <-ch

		switch sig {
		case syscall.SIGINT, syscall.SIGTERM: //stop
			log.Println("starting stop")
			//停止接收信号
			signal.Stop(ch)
			//平滑关闭进程
			err := ser.Shutdown(ctx)
			if err != nil {
				log.Println("stop fail: " + err.Error())
				return
			}
			log.Println("stop done")
			return
		case syscall.SIGUSR2: //reload
			log.Println("starting reload")
			// 先启动新进程
			err := reload()
			if err != nil {
				log.Println("reload fail: " + err.Error())
			}

			//再把老进程平滑关闭
			err = ser.Shutdown(ctx)
			if err != nil {
				log.Println("shutdown old server fail: " + err.Error())
				return
			}
			log.Println("reload done")

			return
		}
	}
}

// 构造http.Server
func getHttpServer(port string, prodFlag bool) *http.Server {
	Boot()
	return &http.Server{
		Addr:    ":" + port,
		Handler: Handler,
	}
}
